using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using Weazel.Microblaze.Simulator.Engine;
using System.Diagnostics;

namespace Weazel.Microblaze.Simulator.Components.Opb
{
    public class Opb : Component, IMemorySpace
    {
        #region Private Classes

        private class MasterData
        {
            public IOpbMaster Master;
            public AutoResetEvent RequestEvent;
            public bool Request;
            public AutoResetEvent GrantEvent;

            public MasterData(IOpbMaster master)
            {
                this.Master = master;
                this.RequestEvent = new AutoResetEvent(false);
                this.GrantEvent = new AutoResetEvent(false);
            }
        }

        private class SlaveData
        {
            public IOpbSlave Slave;
            
            public SlaveData(IOpbSlave slave)
            {
                this.Slave = slave;                
            }
        }

        #endregion 

        public enum OpbCycleStatus
        {
            Idle,
            Busy
        }

        private ClockUpdatedVariable<OpbCycleStatus> status;
        private ClockUpdatedVariable<IOpbMaster> currentOwner;
        private Transaction currentTransaction;
        private ManualResetEvent waitForTransactionEvent = new ManualResetEvent(false);
        private IOpbMaster selectedMaster;
        private uint lowAddress;
        private uint highAddress;

        private Dictionary<IOpbSlave, SlaveData> slaves;
        private Dictionary<IOpbMaster, MasterData> masters;

        #region Properties

        /// <summary>
        /// The current owner of the bus
        /// </summary>
        public IOpbMaster CurrentMaster
        {
            get { return currentOwner.Get(); }
        }

        public bool OperationInProgress
        {
            get { return currentTransaction != null; }
        }
        
        public Transaction CurrentTransaction
        {
            get { return currentTransaction; }
        }

        public OpbCycleStatus Status
        {
            get { return status.Get(); }
        }
        
        public uint LowAddress
        {
            get { return lowAddress; }
            set { lowAddress = value; }
        }
        
        public uint HighAddress
        {
            get { return highAddress; }
            set { highAddress = value; }
        }

        #endregion

        #region Address Comparison 

        private bool addressInSegment(uint address, IMemorySegment segment)
        {
            return address >= segment.LowAddress && address < segment.HighAddress;
        }

        private IOpbSlave getSegment(uint address)
        {
            foreach (IOpbSlave slave in slaves.Keys)
                if (addressInSegment(address, slave))
                    return slave;

            return null;
        }

        #endregion 

        #region Slave and Master Registration

        public void RegisterSlave(IOpbSlave slave)
        {
            slave.Opb = this;

            // check that the segment does not overlap with any
            // existing segments
            foreach (IOpbSlave s in slaves.Keys)
            {
                bool overlap =
                  addressInSegment(slave.LowAddress, s) ||
                  addressInSegment(slave.HighAddress, s);

                if (overlap)
                    throw new MemoryException(Context, MemoryException.ErrorType.AddressOverlap);
            }

            this.slaves.Add(slave, new SlaveData(slave));
        }

        public void RegisterMaster(IOpbMaster master)
        {
            masters.Add(master, new MasterData(master));
        }

        #endregion 

        public Opb(Simulator.Engine.SimulationContext context)
            : base(context)
        {
            this.slaves = new Dictionary<IOpbSlave, SlaveData>();
            this.masters = new Dictionary<IOpbMaster, MasterData>();

            status = new ClockUpdatedVariable<OpbCycleStatus>();
            RegisterClockUpdatedVariable(status);

            currentOwner = new ClockUpdatedVariable<IOpbMaster>();
            RegisterClockUpdatedVariable(currentOwner);
        }
        
        public override string GetStatusString()
        {
            return "n/a";
        }

        public void Load(byte[] bytes, uint address)
        {
            IMemorySegment segment = getSegment(address);

            if (segment == null)
                throw new MemoryException(Context, MemoryException.ErrorType.LoadError);

            if (address + bytes.Length >= segment.HighAddress)
                throw new MemoryException(Context, MemoryException.ErrorType.LoadError);

            segment.Load(bytes, address);
        }

        public void BeginTransaction(Transaction transaction)
        {
            Trace.Assert(transaction != null);
            Trace.Assert(currentTransaction == null);
            Trace.Assert(Status == OpbCycleStatus.Busy);

            // set as current transaction
            this.currentTransaction = transaction;

            Console.WriteLine("setting");
            // notify
            this.waitForTransactionEvent.Set();
        }




        public bool Request(IOpbMaster opbMaster, bool request)
        {
            masters[opbMaster].Request = request;

            // signal
            masters[opbMaster].RequestEvent.Set();

            // stall
            masters[opbMaster].GrantEvent.WaitOne();

            return selectedMaster == opbMaster;
        }

        private WaitHandle[] GetRequestEvents()
        {
            List<WaitHandle> result = new List<WaitHandle>();

            foreach (MasterData data in masters.Values)
                result.Add(data.RequestEvent);

            return result.ToArray();
        }


        private void JoinWithMasters(bool arbitrate)
        {
            // wait for all masters to signal if they want to use the bus
            WaitHandle[] handles = GetRequestEvents();

            // wait for all masters to explicitly set the "request" flag
            WaitHandle.WaitAll(handles);

            selectedMaster = null;

            if (arbitrate)
            {
                bool found = false;
                foreach (MasterData data in masters.Values)
                {
                    if (data.Request)
                    {
                        selectedMaster = data.Master;
                        currentOwner.Set(data.Master);
                        found = true;
                    }
                }

                // no requests?
                if (!found)
                    currentOwner.Set(null);
            }

            // release masters
            foreach (MasterData data in masters.Values)
            {
                data.Request = false;
                data.GrantEvent.Set();
            }
        }

        public override void Run()
        {            
            while (true)
            {
                if (Status == OpbCycleStatus.Busy)
                {
                    if (currentTransaction == null)
                    {
                        currentTransaction = CurrentMaster.GetTransaction();

                        Trace.Assert(currentTransaction != null);

                        bool found = false;
                        foreach (IOpbSlave slave in slaves.Keys)
                            if (addressInSegment(currentTransaction.Address, slave))
                            {
                                slave.BeginTransaction(currentTransaction);
                                found = true;
                            }
                            else
                            {
                                slave.BeginIdleTransaction();
                            }


                        if (!found)
                            throw new MethodAccessException(string.Format("No device present at address: 0x{0:X}", currentTransaction.Address));
                    }
                    else
                    {                        
                        if (currentTransaction.Status == Transaction.TransactionStatus.Complete)
                        {
                            Trace.Assert(currentTransaction.Status == Transaction.TransactionStatus.Complete);
                            status.Set(OpbCycleStatus.Idle);
                            currentTransaction = null;
                        }
                    }

                    JoinWithMasters(false);
                }
                else if (Status == OpbCycleStatus.Idle)
                {
                    JoinWithMasters(true);
                    status.Set(selectedMaster != null ? OpbCycleStatus.Busy : OpbCycleStatus.Idle);
                }
                else
                {
                    throw new NotSupportedException();
                }

                WaitClock();

                /*
                if (currentTransaction != null)
                {
                    Trace.Assert(Status == OpbCycleStatus.Busy);

                    bool found = false;

                    foreach (IOpbSlave slave in slaves.Keys)
                        if (addressInSegment(currentTransaction.Address, slave))
                        {
                            slave.BeginTransaction(currentTransaction);
                            found = true;
                        }
                        else
                        {
                            slave.BeginIdleTransaction();
                        }


                    if (!found)
                        throw new MethodAccessException(string.Format("No device present at address: 0x{0:X}", currentTransaction.Address));

                }




                foreach (SlaveData data in slaves.Values)
                    data.OperationCompleteEvent.Set();



                if (currentTransaction != null)
                {
                    if (currentTransaction.Status == Transaction.TransactionStatus.Complete)
                    {
                        currentTransaction = null;
                        // no "current" master
                        currentOwner.Set(null);
                    }
                }

                selectedMaster = null;

                if(currentTransaction == null)
                {                    
                    // select the next "current" master (for the next transaction)
                    foreach (MasterData data in masters.Values)
                    {
                        if (data.Request)
                        {
                            selectedMaster = data.Master;
                            currentOwner.Set(data.Master);
                            status.Set(OpbCycleStatus.Busy);
                            break;
                        }
                    }

                    if (selectedMaster == null)
                    {
                        // no master requests
                        status.Set(OpbCycleStatus.Idle);
                    }
                }
                


                WaitClock();
                 */
            }
        }
    }
}
